﻿#region Copyright

// // The contents of this file are subject to the Mozilla Public License
// // Version 1.1 (the "License"); you may not use this file except in compliance
// // with the License. You may obtain a copy of the License at
// //   
// // http://www.mozilla.org/MPL/
// //   
// // Software distributed under the License is distributed on an "AS IS"
// // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// // License for the specific language governing rights and limitations under 
// // the License.
// //   
// // The Initial Developer of the Original Code is Robert Smyth.
// // Portions created by Robert Smyth are Copyright (C) 2008-2013.
// //   
// // All Rights Reserved.

#endregion

using System;
using System.Diagnostics;
using System.Timers;
using NDependencyInjection.interfaces;
using NLog;
using NoeticTools.NWorkflow.DotNetInterop;
using NoeticTools.NWorkflow.ProgressMonitoring;
using Timer = System.Timers.Timer;


namespace NoeticTools.NWorkflow.Steps
{
    public partial class Step<TResult, TStepResult> : IStep
    {
        internal readonly Logger _logger;
        private readonly object _syncRoot = new object();
        private readonly ITimer _timer;
        private readonly Stopwatch _diagnosticsTimer;
        private readonly Timer _progressTickTimer;

        public Step(IWorkflowRunner<TResult> workflowRunner, IWorkflowRules<TResult> workflowRules, string name, ISystemDefinition system)
        {
            _workflowRules = workflowRules;
            _workflowRunner = workflowRunner;
            System = system;
            StepRulesName = name;
            ProgressAvailable = true;
            OnProgressPercentage = 0.01;

            _logger = LogManager.GetLogger(string.Format("NWorkflow.Step({0})".Replace(' ', '_'), StepRulesName));
            CurrentResult = default(TStepResult);

            StepRuleProgress = new ProgressInfo {Total = 1.0};
            Progress = new StepProgressMonitor(StepRuleProgress, workflowRunner, this,
                                               StepRuleProgress.ProgressAvailable, _logger);

            _timer = new NoeticTools.NWorkflow.DotNetInterop.Timer(new Timer());
            _timer.AutoReset = false;
            _timer.Elapsed += OnStepTimeout;

            _progressTickTimer = new Timer {Interval = 500, AutoReset = false};
            _progressTickTimer.Elapsed += ProgressTickTimerElapsed;
            _diagnosticsTimer = new Stopwatch();
        }

        void ProgressTickTimerElapsed(object sender, ElapsedEventArgs e)
        {
            var completed = double.NaN;
            lock (_syncRoot)
            {
                if (StepRuleProgress.Total > 0.0)
                {
                    var elapsed = (double) _diagnosticsTimer.Elapsed.TotalSeconds;
                    completed = Math.Min(1.0, elapsed/StepRuleProgress.Total);
                }
                _progressTickTimer.Start();
            }

            if (!double.IsNaN(completed))
            {
                StepRuleProgress.Completed = completed;
                Progress.Completed = completed;
            }
        }

        private TStepResult CurrentResult { get; set; }

        public string Name
        {
            get { return StepRulesName; }
        }

        public void Enter()
        {
            _diagnosticsTimer.Reset();
            _diagnosticsTimer.Start();
            Progress.Completed = 0.0;
            if (TimedProgress)
            {
                _progressTickTimer.Start();
            }
            RunActions(GetOnEntryActions());
        }

        public void Leave()
        {
            lock (_syncRoot)
            {
                Progress.Completed = 1.0;
                StopTimers();
                _diagnosticsTimer.Stop();
                _logger.Debug("Step '{0}' ran for {1} seconds", Name, _diagnosticsTimer.Elapsed.TotalSeconds);
                RunActions(GetOnLeavingActions());
            }
        }

        public IProgress Progress { get; protected set; }

        /// <summary>
        ///     Cancel the activity without processing any rules or sending any notifications.
        /// </summary>
        public void Abort()
        {
            StopTimers();
        }

        public void StartTimer(TimeSpan timeLimit)
        {
            lock (_syncRoot)
            {
                if (_timer.Enabled)
                {
                    _timer.Stop();
                }
                _timer.Interval = timeLimit.TotalMilliseconds;
                _timer.Start();
            }
        }

        public void SetResult(object result)
        {
            OnStepResult((TStepResult) result);
        }

        public void OnEventFired(object eventValue)
        {
            RunActions(GetOnEventActions(eventValue));
        }

        private void OnStepTimeout(object sender, ElapsedEventArgs e)
        {
            var onTimeoutActions = GetOnTimeoutActions();
            _logger.Debug("Timeout.");
            RunActions(onTimeoutActions);
        }

        private void OnStepResult(TStepResult result)
        {
            _logger.Trace("On result {0}", result);

            CurrentResult = result;

            foreach (var action in GetOnResultActions(result))
            {
                action();
            }

            foreach (var action in GetOnReturnActions())
            {
                action();
            }
        }

        private void RunActions(Action[] actions)
        {
            foreach (var action in actions)
            {
                action();
            }
        }

        private void StopTimers()
        {
            lock (_syncRoot)
            {
                if (_timer.Enabled)
                {
                    _timer.Stop();
                    _timer.Close();
                }

                if (_progressTickTimer.Enabled)
                {
                    _progressTickTimer.Stop();
                    _progressTickTimer.Close();
                }
            }
        }
    }
}